---
title: 会话记忆
description: Spring AI 对话机器人会话记忆功能实现
sidebar_position: 2
---

# 会话记忆

在构建对话机器人时，一个重要的功能是让机器人能够"记住"之前的对话内容，这样用户就可以进行连续的对话，而不必在每次提问时重复上下文信息。Spring AI 提供了会话记忆功能来实现这一点。

## 会话记忆的重要性

会话记忆使得对话机器人能够：

1. 理解上下文相关的问题
2. 提供连贯的回答
3. 记住用户之前提供的信息
4. 提供个性化的用户体验

## 实现会话记忆

Spring AI 提供了多种会话记忆的实现方式，下面我们将介绍如何使用它们。

### 基本设置

首先，我们需要创建一个支持会话记忆的服务：

```java
package com.example.chatbot.service;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryProvider;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.UUID;

@Service
public class ChatbotMemoryService {

    private final ChatClient chatClient;
    private final ChatMemoryProvider chatMemoryProvider;
    
    public ChatbotMemoryService(ChatClient chatClient, ChatMemoryProvider chatMemoryProvider) {
        this.chatClient = chatClient;
        this.chatMemoryProvider = chatMemoryProvider;
    }
    
    public String chat(String userId, String userMessage) {
        // 获取或创建用户的会话记忆
        ChatMemory memory = chatMemoryProvider.getChatMemory(userId);
        
        // 添加用户消息到记忆
        memory.add(new UserMessage(userMessage));
        
        // 创建系统提示
        String systemPromptText = "你是一个友好的助手，能够记住用户的对话历史并提供连贯的回答。";
        SystemPromptTemplate systemPrompt = new SystemPromptTemplate(systemPromptText);
        
        // 创建包含记忆的提示
        Prompt prompt = new Prompt(systemPrompt.getContents(), memory.getMessages());
        
        // 获取响应
        ChatResponse response = chatClient.call(prompt);
        
        // 自动将AI回复添加到记忆中
        String reply = response.getResult().getOutput().getContent();
        
        return reply;
    }
    
    // 创建新的会话ID
    public String createNewSession() {
        return UUID.randomUUID().toString();
    }
    
    // 清除特定用户的会话记忆
    public void clearMemory(String userId) {
        ChatMemory memory = chatMemoryProvider.getChatMemory(userId);
        memory.clear();
    }
}
```

### 配置会话记忆提供者

在Spring配置中，我们需要设置一个会话记忆提供者：

```java
@Configuration
public class ChatMemoryConfig {

    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        // 使用内存存储的会话记忆提供者
        return new InMemoryChatMemoryProvider();
    }
    
    // 或者使用Redis存储的会话记忆提供者（需要添加spring-boot-starter-data-redis依赖）
    /*
    @Bean
    public ChatMemoryProvider redisChatMemoryProvider(RedisTemplate<String, Object> redisTemplate) {
        return new RedisChatMemoryProvider(redisTemplate);
    }
    */
}
```

### 创建REST API

接下来，创建一个控制器来暴露带有会话记忆的聊天API：

```java
@RestController
@RequestMapping("/api/chat")
public class ChatbotMemoryController {

    private final ChatbotMemoryService chatbotMemoryService;
    
    public ChatbotMemoryController(ChatbotMemoryService chatbotMemoryService) {
        this.chatbotMemoryService = chatbotMemoryService;
    }
    
    @PostMapping("/memory")
    public ChatResponse chatWithMemory(@RequestBody ChatRequest request) {
        // 如果没有会话ID，创建一个新的
        String sessionId = request.getSessionId();
        if (sessionId == null || sessionId.isEmpty()) {
            sessionId = chatbotMemoryService.createNewSession();
        }
        
        String response = chatbotMemoryService.chat(sessionId, request.getMessage());
        return new ChatResponse(response, sessionId);
    }
    
    @DeleteMapping("/memory/{sessionId}")
    public ResponseEntity<Void> clearMemory(@PathVariable String sessionId) {
        chatbotMemoryService.clearMemory(sessionId);
        return ResponseEntity.ok().build();
    }
    
    // 请求和响应的数据模型
    public static class ChatRequest {
        private String message;
        private String sessionId;
        
        // Getters and Setters
        public String getMessage() {
            return message;
        }
        
        public void setMessage(String message) {
            this.message = message;
        }
        
        public String getSessionId() {
            return sessionId;
        }
        
        public void setSessionId(String sessionId) {
            this.sessionId = sessionId;
        }
    }
    
    public static class ChatResponse {
        private String reply;
        private String sessionId;
        
        public ChatResponse(String reply, String sessionId) {
            this.reply = reply;
            this.sessionId = sessionId;
        }
        
        // Getters
        public String getReply() {
            return reply;
        }
        
        public String getSessionId() {
            return sessionId;
        }
    }
}
```

## 高级会话记忆功能

### 记忆窗口大小限制

为了避免上下文过长导致的性能问题和成本增加，可以限制记忆的消息数量：

```java
@Configuration
public class AdvancedChatMemoryConfig {

    @Bean
    public ChatMemoryProvider windowedChatMemoryProvider() {
        return new WindowedChatMemoryProvider(10); // 只保留最近的10条消息
    }
}
```

### 记忆摘要

对于长对话，可以使用摘要功能来压缩历史记忆：

```java
@Service
public class SummarizingChatService {

    private final ChatClient chatClient;
    private final ChatMemoryProvider chatMemoryProvider;
    
    // 当消息超过这个数量时，进行摘要
    private static final int SUMMARIZE_THRESHOLD = 15;
    
    public String chat(String userId, String userMessage) {
        ChatMemory memory = chatMemoryProvider.getChatMemory(userId);
        memory.add(new UserMessage(userMessage));
        
        // 检查是否需要摘要
        if (memory.getMessages().size() > SUMMARIZE_THRESHOLD) {
            summarizeMemory(memory);
        }
        
        // 其余代码与之前相同...
        
        return reply;
    }
    
    private void summarizeMemory(ChatMemory memory) {
        // 创建一个摘要提示
        String summarizePrompt = "请总结以下对话的要点，保持简洁但不遗漏重要信息：";
        
        // 获取所有历史消息
        List<Message> messages = memory.getMessages();
        
        // 创建摘要请求
        Prompt prompt = new Prompt(new UserMessage(summarizePrompt + 
                                                  messages.stream()
                                                         .map(Message::getContent)
                                                         .collect(Collectors.joining("\n"))));
        
        // 获取摘要
        ChatResponse summaryResponse = chatClient.call(prompt);
        String summary = summaryResponse.getResult().getOutput().getContent();
        
        // 清除旧记忆
        memory.clear();
        
        // 添加摘要作为系统消息
        memory.add(new SystemMessage("对话历史摘要: " + summary));
    }
}
```

## 持久化会话记忆

在生产环境中，您可能需要将会话记忆持久化到数据库中。以下是使用Spring Data JPA实现的示例：

```java
// 会话记忆实体
@Entity
public class ChatMemoryEntity {
    @Id
    private String sessionId;
    
    @Lob
    private String serializedMemory;
    
    private Instant lastUpdated;
    
    // Getters and Setters
}

// 会话记忆仓库
@Repository
public interface ChatMemoryRepository extends JpaRepository<ChatMemoryEntity, String> {
    List<ChatMemoryEntity> findByLastUpdatedBefore(Instant time);
}

// 持久化会话记忆提供者
@Service
public class PersistentChatMemoryProvider implements ChatMemoryProvider {

    private final ChatMemoryRepository repository;
    private final ObjectMapper objectMapper;
    
    // 实现获取、保存和清除记忆的方法
    // ...
}
```

## 前端实现

下面是一个简单的前端实现，用于与带有会话记忆的聊天API交互：

```html
<!DOCTYPE html>
<html>
<head>
    <title>对话机器人 - 带记忆</title>
    <style>
        /* 与之前相同的样式 */
    </style>
</head>
<body>
    <h1>Spring AI 对话机器人 (带记忆)</h1>
    <div id="chatbox"></div>
    <form id="message-form">
        <input type="text" id="message-input" placeholder="输入消息..." />
        <button type="submit">发送</button>
    </form>
    <button id="clear-memory">清除对话历史</button>

    <script>
        // 存储会话ID
        let sessionId = localStorage.getItem('chatSessionId') || '';
        
        document.getElementById('message-form').addEventListener('submit', async function(e) {
            e.preventDefault();
            
            const messageInput = document.getElementById('message-input');
            const message = messageInput.value;
            if (!message) return;
            
            addMessage('user', message);
            messageInput.value = '';
            
            try {
                const response = await fetch('/api/chat/memory', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ 
                        message, 
                        sessionId 
                    }),
                });
                
                const data = await response.json();
                
                // 保存会话ID
                sessionId = data.sessionId;
                localStorage.setItem('chatSessionId', sessionId);
                
                addMessage('bot', data.reply);
            } catch (error) {
                console.error('Error:', error);
                addMessage('bot', '抱歉，发生了错误。请稍后再试。');
            }
        });
        
        document.getElementById('clear-memory').addEventListener('click', async function() {
            if (sessionId) {
                try {
                    await fetch(`/api/chat/memory/${sessionId}`, {
                        method: 'DELETE'
                    });
                    
                    // 清除UI和本地存储
                    document.getElementById('chatbox').innerHTML = '';
                    localStorage.removeItem('chatSessionId');
                    sessionId = '';
                    
                    addMessage('bot', '对话历史已清除。');
                } catch (error) {
                    console.error('Error clearing memory:', error);
                }
            }
        });
        
        function addMessage(type, content) {
            const chatbox = document.getElementById('chatbox');
            const messageDiv = document.createElement('div');
            messageDiv.className = type === 'user' ? 'user-message' : 'bot-message';
            messageDiv.textContent = content;
            chatbox.appendChild(messageDiv);
            chatbox.scrollTop = chatbox.scrollHeight;
        }
    </script>
</body>
</html>
```

## 最佳实践

1. **设置合理的记忆窗口大小** - 避免上下文过长导致的性能问题和成本增加
2. **实现记忆摘要功能** - 对于长对话，定期生成摘要以保持关键信息
3. **持久化会话记忆** - 在生产环境中使用数据库或Redis等持久化存储
4. **设置会话过期时间** - 定期清理长时间不活跃的会话记忆
5. **提供清除记忆的功能** - 允许用户在需要时重新开始对话

## 下一步

了解了会话记忆功能后，您可以继续探索：

1. [多模态功能](./multimodal)，让机器人处理图像等多种类型的输入
2. [提示词工程](../simulator/prompt-engineering)，优化机器人的回答质量

## 参考资料

- [Spring AI 官方文档 - 会话记忆](https://docs.spring.io/spring-ai/reference/api/memory.html)
- [Spring AI GitHub 仓库](https://github.com/spring-projects/spring-ai) 